// Package storage provides metadata storage backend implementations
package storage

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	_ "github.com/mattn/go-sqlite3" // SQLite driver
	"go.etcd.io/bbolt"
	"github.com/smart-snapshotter/pkg/types"
)

// MetadataBackend represents a metadata storage backend
type MetadataBackend interface {
	StoreMetadata(ctx context.Context, key string, metadata map[string]string) error
	LoadMetadata(ctx context.Context, key string) (map[string]string, error)
	DeleteMetadata(ctx context.Context, key string) error
	ListMetadata(ctx context.Context, prefix string) ([]string, error)
	Close() error
}

// SQLiteMetadataBackend provides SQLite-based metadata storage
type SQLiteMetadataBackend struct {
	db *sql.DB
	path string
}

// NewSQLiteMetadataBackend creates a new SQLite metadata backend
func NewSQLiteMetadataBackend(path string) (*SQLiteMetadataBackend, error) {
	// Ensure directory exists
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create directory: %w", err)
	}

	// Open database
	db, err := sql.Open("sqlite3", path)
	if err != nil {
		return nil, fmt.Errorf("failed to open SQLite database: %w", err)
	}

	backend := &SQLiteMetadataBackend{
		db:   db,
		path: path,
	}

	// Initialize schema
	if err := backend.initSchema(); err != nil {
		db.Close()
		return nil, fmt.Errorf("failed to initialize schema: %w", err)
	}

	return backend, nil
}

// initSchema initializes the SQLite schema
func (s *SQLiteMetadataBackend) initSchema() error {
	query := `
	CREATE TABLE IF NOT EXISTS metadata (
		key TEXT PRIMARY KEY,
		value TEXT NOT NULL,
		created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
		updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
	);

	CREATE INDEX IF NOT EXISTS idx_metadata_key ON metadata(key);
	`

	_, err := s.db.Exec(query)
	if err != nil {
		return fmt.Errorf("failed to create schema: %w", err)
	}

	return nil
}

// StoreMetadata stores metadata in SQLite
func (s *SQLiteMetadataBackend) StoreMetadata(ctx context.Context, key string, metadata map[string]string) error {
	// Convert metadata to JSON
	jsonData, err := json.Marshal(metadata)
	if err != nil {
		return fmt.Errorf("failed to marshal metadata: %w", err)
	}

	query := `
	INSERT OR REPLACE INTO metadata (key, value, updated_at)
	VALUES (?, ?, CURRENT_TIMESTAMP)
	`

	_, err = s.db.ExecContext(ctx, query, key, string(jsonData))
	if err != nil {
		return fmt.Errorf("failed to store metadata: %w", err)
	}

	return nil
}

// LoadMetadata loads metadata from SQLite
func (s *SQLiteMetadataBackend) LoadMetadata(ctx context.Context, key string) (map[string]string, error) {
	query := `SELECT value FROM metadata WHERE key = ?`

	var jsonData string
	err := s.db.QueryRowContext(ctx, query, key).Scan(&jsonData)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, types.ErrStorageNotFound
		}
		return nil, fmt.Errorf("failed to load metadata: %w", err)
	}

	var metadata map[string]string
	if err := json.Unmarshal([]byte(jsonData), &metadata); err != nil {
		return nil, fmt.Errorf("failed to unmarshal metadata: %w", err)
	}

	return metadata, nil
}

// DeleteMetadata deletes metadata from SQLite
func (s *SQLiteMetadataBackend) DeleteMetadata(ctx context.Context, key string) error {
	query := `DELETE FROM metadata WHERE key = ?`

	result, err := s.db.ExecContext(ctx, query, key)
	if err != nil {
		return fmt.Errorf("failed to delete metadata: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rowsAffected == 0 {
		return types.ErrStorageNotFound
	}

	return nil
}

// ListMetadata lists metadata keys with a given prefix
func (s *SQLiteMetadataBackend) ListMetadata(ctx context.Context, prefix string) ([]string, error) {
	query := `SELECT key FROM metadata WHERE key LIKE ? ORDER BY key`

	rows, err := s.db.QueryContext(ctx, query, prefix+"%")
	if err != nil {
		return nil, fmt.Errorf("failed to list metadata: %w", err)
	}
	defer rows.Close()

	var keys []string
	for rows.Next() {
		var key string
		if err := rows.Scan(&key); err != nil {
			return nil, fmt.Errorf("failed to scan key: %w", err)
		}
		keys = append(keys, key)
	}

	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("rows error: %w", err)
	}

	return keys, nil
}

// Close closes the SQLite database
func (s *SQLiteMetadataBackend) Close() error {
	return s.db.Close()
}

// BoltDBMetadataBackend provides BoltDB-based metadata storage
type BoltDBMetadataBackend struct {
	db *bbolt.DB
	path string
}

// NewBoltDBMetadataBackend creates a new BoltDB metadata backend
func NewBoltDBMetadataBackend(path string) (*BoltDBMetadataBackend, error) {
	// Ensure directory exists
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create directory: %w", err)
	}

	// Remove existing file to avoid corruption issues
	os.Remove(path)

	// Open database
	db, err := bbolt.Open(path, 0600, &bbolt.Options{Timeout: 1 * time.Second})
	if err != nil {
		return nil, fmt.Errorf("failed to open BoltDB database: %w", err)
	}

	backend := &BoltDBMetadataBackend{
		db:   db,
		path: path,
	}

	// Initialize bucket
	if err := backend.initBucket(); err != nil {
		db.Close()
		return nil, fmt.Errorf("failed to initialize bucket: %w", err)
	}

	return backend, nil
}

// initBucket initializes the BoltDB bucket
func (b *BoltDBMetadataBackend) initBucket() error {
	return b.db.Update(func(tx *bbolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte("metadata"))
		return err
	})
}

// StoreMetadata stores metadata in BoltDB
func (b *BoltDBMetadataBackend) StoreMetadata(ctx context.Context, key string, metadata map[string]string) error {
	// Convert metadata to JSON
	jsonData, err := json.Marshal(metadata)
	if err != nil {
		return fmt.Errorf("failed to marshal metadata: %w", err)
	}

	return b.db.Update(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket([]byte("metadata"))
		return bucket.Put([]byte(key), jsonData)
	})
}

// LoadMetadata loads metadata from BoltDB
func (b *BoltDBMetadataBackend) LoadMetadata(ctx context.Context, key string) (map[string]string, error) {
	var metadata map[string]string

	err := b.db.View(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket([]byte("metadata"))
		data := bucket.Get([]byte(key))

		if data == nil {
			return types.ErrStorageNotFound
		}

		return json.Unmarshal(data, &metadata)
	})

	if err != nil {
		return nil, fmt.Errorf("failed to load metadata: %w", err)
	}

	return metadata, nil
}

// DeleteMetadata deletes metadata from BoltDB
func (b *BoltDBMetadataBackend) DeleteMetadata(ctx context.Context, key string) error {
	return b.db.Update(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket([]byte("metadata"))
		data := bucket.Get([]byte(key))

		if data == nil {
			return types.ErrStorageNotFound
		}

		return bucket.Delete([]byte(key))
	})
}

// ListMetadata lists metadata keys with a given prefix
func (b *BoltDBMetadataBackend) ListMetadata(ctx context.Context, prefix string) ([]string, error) {
	var keys []string

	err := b.db.View(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket([]byte("metadata"))

		c := bucket.Cursor()
		prefixBytes := []byte(prefix)

		for k, _ := c.Seek(prefixBytes); k != nil && len(k) >= len(prefixBytes) && string(k[:len(prefixBytes)]) == prefix; k, _ = c.Next() {
			keys = append(keys, string(k))
		}

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to list metadata: %w", err)
	}

	return keys, nil
}

// Close closes the BoltDB database
func (b *BoltDBMetadataBackend) Close() error {
	return b.db.Close()
}

// FileMetadataBackend provides file-based metadata storage
type FileMetadataBackend struct {
	basePath string
}

// NewFileMetadataBackend creates a new file-based metadata backend
func NewFileMetadataBackend(basePath string) (*FileMetadataBackend, error) {
	// Ensure directory exists
	if err := os.MkdirAll(basePath, 0755); err != nil {
		return nil, fmt.Errorf("failed to create directory: %w", err)
	}

	return &FileMetadataBackend{
		basePath: basePath,
	}, nil
}

// StoreMetadata stores metadata in JSON files
func (f *FileMetadataBackend) StoreMetadata(ctx context.Context, key string, metadata map[string]string) error {
	metadataPath := filepath.Join(f.basePath, key+".json")

	// Create directory if not exists
	dir := filepath.Dir(metadataPath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create directory: %w", err)
	}

	// Convert to JSON
	jsonData, err := json.MarshalIndent(metadata, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal metadata: %w", err)
	}

	// Write atomically
	tempFile := metadataPath + ".tmp"
	if err := os.WriteFile(tempFile, jsonData, 0644); err != nil {
		return fmt.Errorf("failed to write metadata: %w", err)
	}

	// Atomic rename
	if err := os.Rename(tempFile, metadataPath); err != nil {
		os.Remove(tempFile) // Clean up temp file
		return fmt.Errorf("failed to rename metadata file: %w", err)
	}

	return nil
}

// LoadMetadata loads metadata from JSON files
func (f *FileMetadataBackend) LoadMetadata(ctx context.Context, key string) (map[string]string, error) {
	metadataPath := filepath.Join(f.basePath, key+".json")

	data, err := os.ReadFile(metadataPath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, types.ErrStorageNotFound
		}
		return nil, fmt.Errorf("failed to read metadata: %w", err)
	}

	var metadata map[string]string
	if err := json.Unmarshal(data, &metadata); err != nil {
		return nil, fmt.Errorf("failed to unmarshal metadata: %w", err)
	}

	return metadata, nil
}

// DeleteMetadata deletes metadata JSON files
func (f *FileMetadataBackend) DeleteMetadata(ctx context.Context, key string) error {
	metadataPath := filepath.Join(f.basePath, key+".json")

	if err := os.Remove(metadataPath); err != nil {
		if os.IsNotExist(err) {
			return types.ErrStorageNotFound
		}
		return fmt.Errorf("failed to delete metadata: %w", err)
	}

	return nil
}

// ListMetadata lists metadata keys with a given prefix
func (f *FileMetadataBackend) ListMetadata(ctx context.Context, prefix string) ([]string, error) {
	var keys []string

	err := filepath.Walk(f.basePath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Skip directories
		if info.IsDir() {
			return nil
		}

		// Check if it's a JSON file
		if filepath.Ext(path) != ".json" {
			return nil
		}

		// Get relative path and remove .json extension
		relPath, err := filepath.Rel(f.basePath, path)
		if err != nil {
			return err
		}

		key := strings.TrimSuffix(relPath, ".json")

		// Check prefix
		if strings.HasPrefix(key, prefix) {
			keys = append(keys, key)
		}

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to walk directory: %w", err)
	}

	return keys, nil
}

// Close closes the file metadata backend (no-op)
func (f *FileMetadataBackend) Close() error {
	return nil
}

// NewMetadataBackend creates a new metadata backend based on type
func NewMetadataBackend(backendType, path string) (MetadataBackend, error) {
	switch backendType {
	case "sqlite":
		return NewSQLiteMetadataBackend(path)
	case "boltdb":
		return NewBoltDBMetadataBackend(path)
	case "file":
		return NewFileMetadataBackend(path)
	default:
		return nil, fmt.Errorf("unsupported metadata backend type: %s", backendType)
	}
}